% Autore: Mattia Coccato
% Argomenti: discussione sui trigger impiegati


\chapter{Sviluppo dei Trigger}
\label{chTrigger}

Molti dei vincoli esterni imposti dal committente non sono esprimibili mediante costrutti primitivi del modello relazionale; si è reso perciò necessaria l'introduzione dei \emph{trigger}, funzioni che vengono eseguite dalla base di dati a seguito di operazioni di inserimento o modifica su particolari relazioni, rendendola così una base di dati \emph{attiva}, per assicurare in ogni momento che i dati memorizzati siano tra loro consistenti.

\section{Vincolo: ``I biglietti devono essere univoci''}
\label{parBigliettiUnivoci}
Il primo problema che ci siamo posti è stato l'impedire, a seguito di operazioni concorrenti, la prenotazione per uno stesso posto da parte di due persone distinte, emettendo così due biglietti per uno stesso spettacolo e per lo stesso posto a sedere. Tale problema non può essere risolto con un vincolo di univocità sugli attributi \texttt{(codiceSpettacolo, codicePoltrona)} per la semplice ragione che il biglietto può essere annullato; nel caso si includesse anche l'attributo \texttt{valido} -- che per l'appunto esprime la validità del biglietto -- si risolverebbe il conflitto, ma si impedirebbe di avere più biglietti annullati.

La soluzione è stata l'introduzione di un trigger che, a monte dell'inserimento della tupla rappresentante il biglietto, esegue un controllo sulla presenza di un eventuale biglietto valido collidente, e lo segnala all'utente rifiutando l'inserimento che porterebbe ad una situazione di inconsistenza.

\pagebreak

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-biglietto, caption=Trigger e relativa funzione per il controllo dell'univocità dei biglietti]
CREATE FUNCTION controllaUnivocitaBiglietto() RETURNS TRIGGER AS $$
BEGIN
	-- cerco se c'e' un altro biglietto valido con la stessa poltrona
	PERFORM NumBiglietto
		FROM Biglietto AS B
		WHERE B.IDPoltrona = NEW.IDPoltrona AND B.Valido = TRUE
			AND B.CodiceSpettacolo = NEW.CodiceSpettacolo
			AND B.NumBiglietto <> NEW.NumBiglietto; --permetto le modifiche al singolo biglietto
	IF FOUND THEN
		RAISE EXCEPTION 'Seduta occupata per quello spettacolo!!';
	END IF;

	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "bigliettoUnivoco" BEFORE INSERT OR UPDATE 
	ON Biglietto
	FOR EACH ROW
	EXECUTE PROCEDURE controllaUnivocitaBiglietto();
\end{lstlisting}

\section{Vincolo: ``Non si possono acquistare più biglietti di quanti definiti dall'abbonamento''}
\label{parAbbonamentoLimitato}
Il problema successivo riguarda la gestione degli abbonamenti; quando si utilizza l'abbonamento il biglietto viene emesso a costo zero, ma il numero di tali biglietti deve essere ovviamente limitato al monte disponibile all'acquisto dell'abbonamento stesso. Vi possono essere due soluzioni percorribili per garantire in questo frangente la consistenza dei dati:
\begin{enumerate}
	\item Si mantiene un contatore di biglietti rimasti per ogni abbonamento emesso; tale contatore è decrementato in un unica transazione al momento dell'emissione del biglietto, se maggiore di zero.
	\item All'inserimento del biglietto si contano quanti ne sono stati emessi, e si rifiuta l'inserimento se il monte biglietti è esaurito.
\end{enumerate}

La prima soluzione può presentare (in maniera fortemente dipendente dagli indici) il vantaggio di un minor carico elaborativo per la base di dati, in quanto si basa sulla lettura di un unico campo da una tupla in una relazione, mentre la seconda prevede lo scorrimento (dell'indice) delle tuple per trovare quelle corrispondenti e in seguito provvedere al loro conteggio. Tuttavia, la prima soluzione è stata scartata sin dall'inizio per due fondamentali motivi:
\begin{itemize}
	\item La relazione su cui si basa il conteggio ha i record costituiti da pochi campi interi, quindi molto agile da scorrere, anche con l'aiuto degli indici di chiave primaria che comprendono il campo codice abbonamento, e
	\item L'avere un campo separato dal conteggio può portare ad una situazione di inconsistenza qualora venga alterato tale valore.
\end{itemize}

Il numero totale degli ingressi è preso dalla relazione \texttt{Abbonamento} e non dal \texttt{Listino} per dar la possibilità di variare quest'ultimo senza ripercussioni sugli abbonamenti già regolarmente acquistati dai clienti nel passato; tale totale viene infatti copiato dal corrispondente listino all'emissione del carnet.

%\pagebreak

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-abbonamento, caption=Trigger e relativa funzione per il controllo del consumo degli abbonamenti]
CREATE FUNCTION controllaMassimoUsoDiAbbonamento() RETURNS TRIGGER AS $$
DECLARE
	ingressiUsati INT;
	totaleIngressi INT;
BEGIN
	SELECT INTO ingressiUsati, totaleIngressi
		COUNT(B.NumBiglietto), A.TotaleIngressi
			FROM Biglietto AS B INNER JOIN R_Consuma ON B.NumBiglietto = R_Consuma.NumeroBiglietto
				INNER JOIN Abbonamento AS A ON R_Consuma.NumAbbonam = A.Numero
			WHERE B.Valido = TRUE AND B.Pagato = TRUE AND A.Numero = NEW.NumAbbonam
			GROUP BY A.Numero, A.TotaleIngressi;

	IF ingressiUsati >= totaleIngressi THEN
		RAISE EXCEPTION 'Abbonamento Esaurito';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "usoAbbonamenti" BEFORE INSERT OR UPDATE
	ON R_Consuma
	FOR EACH ROW
	EXECUTE PROCEDURE controllaMassimoUsoDiAbbonamento();
\end{lstlisting}

\section{Vincolo: ``Le prenotazioni per singolo utente sono limitate''}
\label{parPrenotazioni}

Per volontà dei gestori del cinema, si è deciso di limitare il massimo numero di prenotazioni (ossia di biglietti emessi ma non ancora pagati) eseguibili da un utente ad un massimo di 5 unità per ogni spettacolo, onde evitare che vengano mantenuti bloccati troppi posti a sedere, quindi perdendo potenziali clienti, senza avere la conferma che l'utente poi si presenti effettivamente allo spettacolo. Il problema non è critico per l'emissione dei biglietti, in quanto quelli non ancora pagati possono essere disabilitati dal software di cassa nei 30 minuti antecedenti l'inizio dello spettacolo, ma lo diventa per gli utenti che vorrebbero riservarsi il posto comodamente da casa.

La funzione svolta è sulla falsa riga di quanto esposto per gli abbonamenti nel paragrafo precedente; viene effettuato il conteggio dei biglietti già emessi e rifiutato l'inserimento qualora ve ne siano in numero eccessivo.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-max-prenotazioni, caption=Trigger e relativa funzione per il controllo delle massime prenotazioni ottenibili da un utente]
CREATE FUNCTION controllaMassimoNumeroPrenotazioni() RETURNS TRIGGER AS $$
DECLARE
	numeroBigliettiPrenotati INT;
BEGIN
	--non opero il controllo se la nuova riga e' di un biglietto non valido (utile per eliminare i biglietti inseriti)
	IF NEW.Valido = FALSE THEN
		RETURN NEW;
	END IF;

	--cerco quanti biglietti 
	SELECT Count(NumBiglietto)::INT INTO numeroBigliettiPrenotati
		FROM Biglietto AS B
		WHERE B.Pagato = FALSE AND B.Valido = TRUE 
			AND B.CodiceSpettacolo = NEW.CodiceSpettacolo
			AND B.IDUtente = NEW.IDUtente;

	IF numeroBigliettiPrenotati >= 5 THEN
		RAISE EXCEPTION 'Ecceduto numero di biglietti prenotati e non pagati!';
	END IF;

	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "massimePrenotazioni" BEFORE INSERT OR UPDATE
	ON Biglietto
	FOR EACH ROW
	EXECUTE PROCEDURE controllaMassimoNumeroPrenotazioni();
\end{lstlisting}

\section{Vincolo: ``I minuti di uso dei proiettori devono essere sufficienti''}
\label{parMinutiProiettori}

La nostra applicazione si occupa anche di tener traccia delle necessità di manutenzione dei proiettori; per tale scopo è necessario tuttavia tenere conto delle ore di uso effettivo delle macchine per provvedere alla loro tempestiva manutenzione ordinaria. Il valore minimo dei minuti di utilizzo è da ricercarsi nell'\texttt{evento} cui lo spettacolo si riferisce, in particolare al suo attributo ``durataProiezione''.
Nel trigger si tiene conto della somma delle ore di uso per spettacolo poiché per alcuni spettacoli può essere conveniente spezzare la riproduzione in due o più proiettori.

Se si utilizzano due o più proiettori tuttavia esiste per certo un momento in cui i dati non sono consistenti, e ciò avviene -- a transazione ancora in corso -- quando non sono state inserite tutte le voci d'uso. Se il controllo avvenisse in quel momento troverebbe (correttamente) la situazione non consistente, ma lo è solamente perché la transazione non è ultimata; è per questo motivo che uno dei trigger in questo caso è marcato \texttt{DEFERRED INITIALLY DEFERRED}, per ritardarne cioè l'esecuzione al completamento della transazione e non all'inserimento della tupla. 

Per questa funzione di controllo si è resa necessaria la stesura di due funzioni, poiché la durata d'uso deve essere validata sia quando si modificano (o inseriscono) i proiettori impiegati in uno spettacolo, sia se le proprietà dell'evento cambiano (ossia se si corregge dopo l'inserimento la durata effettiva di un film). Nelle relazioni intermedie nella catena (\texttt{spettacolo} e \texttt{palinsesto}) si è inserito un controllo di coerenza che impedisca di cambiare l'evento a loro associato.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-uso-proiettori1, caption=Trigger per il controllo della durata di proiezione]
CREATE FUNCTION controllaMinimoUsoProiettori() RETURNS TRIGGER AS $$
DECLARE
	totaleOre INT;
	durataProiezione INT;
BEGIN
	-- calcolo durata uso dei proiettori	
	SELECT SUM(Durata) INTO totaleOre
		FROM R_Proiettato
		WHERE R_Proiettato.CodiceSpettacolo = NEW.CodiceSpettacolo;
	
	-- leggo durata dichiarata
	SELECT Evento.DurataProiezione INTO durataProiezione
		FROM Evento INNER JOIN Palinsesto ON Evento.Codice = Palinsesto.CodiceEvento
			INNER JOIN Spettacolo ON Spettacolo.CodicePalinsesto = Palinsesto.Codice
		WHERE Spettacolo.Codice = NEW.CodiceSpettacolo;

	IF durataProiezione > totaleOre THEN
		RAISE EXCEPTION 'Numero di minuti totali di proiezione inferiori al dichiarato nell''evento';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE FUNCTION controllaMinimoUsoProiettoriFromEvento() RETURNS TRIGGER AS $$
BEGIN
	-- non intervengo se non e' cambiata la durata - minimizzo impatto su BdD
	IF NEW.durataProiezione = OLD.DurataProiezione THEN RETURN NEW; END IF;

	PERFORM
		spettacolo.codice, sum(r_proiettato.durata) as DurataTotale
		FROM Spettacolo INNER JOIN r_Proiettato ON Spettacolo.codice = r_proiettato.codiceSpettacolo
			INNER JOIN Palinsesto ON Palinsesto.codice = Spettacolo.codicePalinsesto
		WHERE Spettacolo.valido = TRUE AND Palinsesto.codiceEvento = NEW.Codice
		GROUP BY spettacolo.codice
		HAVING sum(r_proiettato.durata) < NEW.DurataProiezione;
			

	IF FOUND THEN
		RAISE EXCEPTION 'Numero di minuti totali di proiezione inferiori al dichiarato nell''evento';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE CONSTRAINT TRIGGER "minimoUsoProiettoreR_Proiettato"
	AFTER update OR insert
	ON R_Proiettato
	DEFERRABLE INITIALLY DEFERRED 
	FOR EACH ROW
	EXECUTE PROCEDURE controllaMinimoUsoProiettori();

CREATE CONSTRAINT TRIGGER "minimoUsoProiettoreEvento"
	AFTER update
	ON Evento
	FOR EACH ROW
	EXECUTE PROCEDURE controllaMinimoUsoProiettoriFromEvento();
\end{lstlisting}

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-uso-proiettori2, caption=Trigger per il controllo di coerenza di \texttt{spettacolo} e \texttt{palinsesto}]
CREATE FUNCTION controllaMinimoUsoProiettoriFromSpettacolo() RETURNS TRIGGER AS $$
DECLARE
	totaleOre INT;
	durataProiezione INT;
BEGIN
	IF (NEW.CodicePalinsesto <> OLD.CodicePalinsesto) AND (NEW.Valido = TRUE) THEN
		RAISE EXCEPTION 'Impossibile cambiare voce di palinsesto associata ad uno spettacolo!';
	END IF;

	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE FUNCTION controllaMinimoUsoProiettoriFromPalinsesto() RETURNS TRIGGER AS $$
DECLARE
	durataProiezione INT;
BEGIN
	IF NEW.CodiceEvento <> OLD.CodiceEvento  THEN
		RAISE EXCEPTION 'Impossibile cambiare evento associato ad una voce di palinsesto!';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE CONSTRAINT TRIGGER "minimoUsoProiettoreSpettacolo"
	AFTER update
	ON Spettacolo
	FOR EACH ROW
	EXECUTE PROCEDURE controllaMinimoUsoProiettoriFromSpettacolo();

CREATE CONSTRAINT TRIGGER "minimoUsoProiettorePalinsesto"
	AFTER update
	ON Palinsesto
	FOR EACH ROW
	EXECUTE PROCEDURE controllaMinimoUsoProiettoriFromPalinsesto();
\end{lstlisting}

\section{Vincolo: ``Due spettacoli non si devono sovrapporre nella stessa sala''}
\label{parSpettacoliSovrapposti}

Gli spettacoli che si svolgono nelle sale le mantengono occupate a partire dall'ora di inizio stabilita a palinsesto, e la loro durata è definita dall'attributo \texttt{DurataInSala} dell'\texttt{Evento} corrispondente; potrebbe capitare che gli addetti assegnino sbadatamente due proiezioni che non possono coesistere nella medesima sala, poiché il momento di inizio della successiva si sovrappone alla precedente non ancora ultimata. Ecco che è necessario un trigger che prevenga il verificarsi di tali situazioni, controllando al momento della creazione dello \texttt{spettacolo}.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-spettacoli-sovrapposti, caption=Trigger e relativa funzione per la prevenzione di spettacoli sovrapposti]
CREATE FUNCTION controllaSpettacoliNonInSovrapposizione() RETURNS TRIGGER AS $$
DECLARE
	oraInizioSpettacoloAttuale TIMESTAMP;
	durataSpettacoloAttuale INTERVAL;
BEGIN
	-- ottengo la data di inizio e durata dello spettacolo che sto inserendo/modificando
	SELECT INTO oraInizioSpettacoloAttuale, durataSpettacoloAttuale
		dataOra, evento.duratainsala * INTERVAL '1 minute'
			FROM spettacolo INNER JOIN palinsesto ON spettacolo.codicepalinsesto = palinsesto.codice
				INNER JOIN evento on evento.codice = palinsesto.codiceevento
			WHERE spettacolo.codice = NEW.CodiceSpettacolo;

	PERFORM Spettacolo.Codice
		FROM spettacolo INNER JOIN palinsesto ON spettacolo.codicepalinsesto = palinsesto.codice
			INNER JOIN evento on evento.codice = palinsesto.codiceevento
			INNER JOIN r_proiettato ON r_proiettato.codicespettacolo = spettacolo.codice
		WHERE r_proiettato.idSala = NEW.idSala AND	--spettacolo nella stessa sala
			Spettacolo.codice <> NEW.codiceSpettacolo AND	-- non impedisco l'aggiunta di nuovi proiettori per lo stesso spettacolo
			Spettacolo.valido = TRUE AND
			(palinsesto.dataora, (evento.duratainsala * INTERVAL '1 minute'))
				OVERLAPS (oraInizioSpettacoloAttuale, durataSpettacoloAttuale); 	-- lo spettacolo si sovrappone ad altri

	IF FOUND THEN
		RAISE EXCEPTION 'Lo spettacolo si sovrappone ad un altro definito nella stessa sala';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "spettacoliNonSovrapposti" BEFORE INSERT OR UPDATE
	ON R_Proiettato
	FOR EACH ROW
	EXECUTE PROCEDURE controllaSpettacoliNonInSovrapposizione();
\end{lstlisting}

\section{Vincolo: ``I film possono essere proiettati solo quando noleggiati''}
\label{parNoleggioFilm}

Poiché la proiezione di un film è soggetta al pagamento delle licenze al fornitore, le si desidera rendere possibili solamente nei periodi definiti a contratto col fornitore.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-proiezioni-noleggio, caption=Trigger e relativa funzione per la proiezione di film solo entro contratto noleggio]
CREATE FUNCTION controllaEsistenzaPrestitoPerFilm() RETURNS TRIGGER AS $$
DECLARE
	durataFilm INTERVAL;
BEGIN
	-- verifico se e' un film, recuperando anche la sua durata
	SELECT INTO durataFilm
		evento.durataInSala * INTERVAL '1 minute'
			FROM evento INNER JOIN film ON evento.codice = film.codice
			WHERE evento.codice = NEW.CodiceEvento;

	IF NOT FOUND THEN
		RETURN NEW;	-- non devo verificare nulla se non e' un film - gestione a carico della contabilita'
	END IF;

	IF NEW.in3d THEN
		PERFORM Noleggio.codicefilm, noleggio.dataInizio
			FROM Noleggio
			WHERE Noleggio.codiceFilm = NEW.CodiceEvento AND
				Noleggio.in3d = TRUE AND
				Noleggio.DataInizio <= NEW.dataora AND (New.DataOra + durataFilm) <=  Noleggio.DataFine + INTERVAL '1 day'; 
			-- devo aggiungere un giorno alla data di fine perche' il noleggio si intende fino alle 24:00 dell'ultimo giorno

		IF NOT FOUND THEN
			RAISE EXCEPTION 'Non esiste un noleggio con 3D abilitato che copra questa proiezione';
		END IF;
	ELSE
		PERFORM Noleggio.codicefilm, noleggio.dataInizio
			FROM Noleggio
			WHERE Noleggio.codiceFilm = NEW.CodiceEvento AND
				Noleggio.in2d = TRUE AND
				Noleggio.DataInizio <= NEW.dataora AND (New.DataOra + durataFilm) <=  Noleggio.DataFine + INTERVAL '1 day'; 
			-- devo aggiungere un giorno alla data di fine perche' il noleggio si intende fino alle 24:00 dell'ultimo giorno

		IF NOT FOUND THEN
			RAISE EXCEPTION 'Non esiste un noleggio con 2D abilitato che copra questa proiezione';
		END IF;
	END IF;

	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "esistenzaPrestitoPerFilm" BEFORE INSERT OR UPDATE
	ON Palinsesto
	FOR EACH ROW
	EXECUTE PROCEDURE controllaEsistenzaPrestitoPerFilm();
\end{lstlisting}

Analogamente, in caso di modifica ad un noleggio in corso, per esempio per accorciarne la durata se un film non accoglie le aspettative, è necessario controllare che non esistano palinsesti fuori dai periodi di noleggio pattuito.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-modifica-noleggio, caption=Trigger e relativa funzione in caso di modifica di un noleggio]
CREATE FUNCTION controllaNonPalinsestoOltreDataFine() RETURNS TRIGGER AS $$

BEGIN
	IF NEW.CodiceFilm <> OLD.CodiceFilm THEN
		RAISE EXCEPTION 'Non si puo'' riassegnare un noleggio ad un film diverso';
	END IF;
	IF NEW.DataInizio <> OLD.DataInizio THEN
		RAISE EXCEPTION 'Non si pu0'' modificare la data di inizio di un noleggio';
	END IF;
	-- per primo controllo quelli in 3d

	PERFORM Palinsesto.Codice
		FROM Palinsesto INNER JOIN Evento ON Evento.Codice = Palinsesto.CodiceEvento
		WHERE Palinsesto.codiceEvento = NEW.CodiceFilm AND Palinsesto.in3d = TRUE AND
			(Palinsesto.dataOra + (Evento.durataInSala * interval '1 minute'))
				> ALL (SELECT DataFine + INTERVAL '1 day' FROM Noleggio AS NoleggioSub WHERE NoleggioSub.CodiceFilm = New.CodiceFilm
						AND NoleggioSub.in3d = TRUE AND NoleggioSub.DataInizio <= Palinsesto.DataOra);
	IF FOUND THEN
		RAISE EXCEPTION 'Palinsesti 3D definiti che diverrebbero non coperti da noleggi';
	END IF;

	-- ora i 2D
	
	PERFORM Palinsesto.Codice
		FROM Palinsesto INNER JOIN Evento ON Evento.Codice = Palinsesto.CodiceEvento
		WHERE Palinsesto.codiceEvento = NEW.CodiceFilm AND Palinsesto.in3d = FALSE AND
			(Palinsesto.dataOra + (Evento.durataInSala * interval '1 minute'))
				> ALL (SELECT DataFine + INTERVAL '1 day' FROM Noleggio AS NoleggioSub WHERE NoleggioSub.CodiceFilm = New.CodiceFilm
						AND NoleggioSub.in2d = TRUE AND NoleggioSub.DataInizio <= Palinsesto.DataOra);

	IF FOUND THEN
		RAISE EXCEPTION 'Palinsesti 2D definiti che diverrebbero non coperti da noleggi';
	END IF;

	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "nonPalinsestoOltreDataFine" AFTER UPDATE
	ON Noleggio
	FOR EACH ROW
	EXECUTE PROCEDURE controllaNonPalinsestoOltreDataFine();
\end{lstlisting}

\section{Vincolo: ``Biglietti 3D solo per spettacoli 3D (analogamente per i 2D)''}
\label{parBiglietti3D}
Per precise indicazioni del committente, e per non falsare eventuali statistiche nonché per non svantaggiare i consumatori, le tariffe pensate per le proiezioni tridimensionali non sono applicabili a quelle tradizionali, questo sia per differenze di licenze che di costo di mantenimento dei proiettori impiegati.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-biglietti-3d, caption=Trigger e relativa funzione che controlla la coerenza di biglietti e spettacoli 3D]
CREATE FUNCTION controllaBiglietto3DSoloPerSpettacolo3D() RETURNS TRIGGER AS $$
DECLARE
	palinsesto3d BOOLEAN;
	fascia3d BOOLEAN;
BEGIN
	SELECT INTO palinsesto3d 
		palinsesto.in3d 
			FROM spettacolo INNER JOIN palinsesto ON palinsesto.codice = spettacolo.codicepalinsesto
			WHERE spettacolo.codice = NEW.codicespettacolo;
	SELECT INTO fascia3d 
		in3d FROM tariffa where tariffa.fascia = NEW.fascia;

	IF (NOT palinsesto3d) AND fascia3d THEN
		RAISE EXCEPTION 'Utilizzato biglietto 3D per spettacolo non in 3D!';
	END IF;
	
	IF palinsesto3d AND NOT fascia3d THEN
		RAISE EXCEPTION 'Utilizzato biglietto 2D per spettacolo 3D!';
	END IF;

	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "biglietto3DSoloPerSpettacolo3D" BEFORE INSERT OR UPDATE
	ON Biglietto
	FOR EACH ROW
	EXECUTE PROCEDURE controllaBiglietto3DSoloPerSpettacolo3D();
\end{lstlisting}

\section{Vincolo: ``Gli abbonamenti non possono essere usati per spettacoli 3D''}
\label{parNoAbbonamenti3D}
Sempre per precise scelte commerciali del committente, ci è stato chiesto di precludere l'uso di carnet di biglietti per le proiezioni tridimensionali, poiché lo sconto ulteriore applicato agli abbonamenti non permette di coprirne i costi.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-no-carnet-3d, caption=Trigger e relativa funzione che controlla l'incompatibilità tra abbonamento e spettacolo 3D]
CREATE FUNCTION controllaUsoAbbonamentoNonPerBiglietti3D() RETURNS TRIGGER AS $$
DECLARE
	fascia3d BOOLEAN;
BEGIN

	SELECT INTO fascia3d 
		tariffa.in3d FROM tariffa INNER JOIN biglietto ON biglietto.fascia = tariffa.fascia
			WHERE biglietto.numbiglietto = NEW.numerobiglietto;

	IF fascia3d THEN
		RAISE EXCEPTION 'Utilizzato abbonamento per uno spettacolo 3D!';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER "usoAbbonamentoNonPerBiglietti3D" BEFORE INSERT OR UPDATE
	ON r_consuma
	FOR EACH ROW
	EXECUTE PROCEDURE controllaUsoAbbonamentoNonPerBiglietti3D();
\end{lstlisting}

\section{Vincolo: Non si possono applicare modifiche a spettacoli con biglietti}
\label{parNoModificheSpettacoloConBiglietti}
Questo non è un vincolo espressamente richiesto dal committente, ma la sua necessità è sorta nel momento dell'implementazione dell'applicazione; senza di esso infatti sarebbe possibile cambiare ore di palinsesti o sale anche qualora vi siano prenotazioni in essere dai clienti.

\lstset{language=SQL, keywordstyle=\color{blue}\bfseries, stringstyle=\color{javared}, commentstyle=\color{javadocblue}}
\begin{lstlisting}[label=trigger-no-modifica-spett-biglietti, caption=Trigger e relativa funzione che impedisce modifiche a spettacolo con biglietti]
CREATE FUNCTION controllaBigliettiEmessiPerUpdateSpettacolo() RETURNS TRIGGER AS $$

BEGIN
	PERFORM Biglietto.numbiglietto
		FROM Biglietto WHERE valido = TRUE AND codiceSpettacolo = NEW.Codice;
	
	IF FOUND THEN
		RAISE EXCEPTION 'Non si possono effettuare modifiche ad uno spettacolo con biglietti validi emessi!';
	END IF;
	
	RETURN NEW;
END
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER bigliettiEmessiPerUpdateSpettacolo BEFORE UPDATE
	ON Spettacolo
	FOR EACH ROW
	EXECUTE PROCEDURE controllaBigliettiEmessiPerUpdateSpettacolo();
\end{lstlisting}


